你可能需要的5个Canvas进阶技巧❤ 您所在的位置:网站首页 h5 禁止缓存 你可能需要的5个Canvas进阶技巧❤

你可能需要的5个Canvas进阶技巧❤

2023-09-14 15:23| 来源: 网络整理| 查看: 265

前言

Canvas作为H5的的一个核心功能,在包括可视化和在线编辑等功能上发挥了很大的作用。想要熟练的使用它同样也是难度不小,作为一个经验不多的小菜鸡来说,由于工作中也会经常接触到Canvas,因此这里就总结一些个人会用到的一些小技巧给大家,希望对你有所帮助!

实用技巧 1. 双缓存

其实双缓存是使用Canvas使用到的比较高频的技巧,主要是用于解决Canvas绘制时画面闪烁或者说白屏的问题。

原理分析

在学习这个技巧前,我们首先来分析下,为什么Canvas会出现闪烁或者白屏的问题。我们先看看正常情况下,我们是怎么把图片渲染到Canvas上去的:

获取Image对象,并等待它加载完成 清除上一帧的画布 调用ctx.drawImage接口绘制图片 按理说,清除画布和绘制图片都是同步的过程,那么为什么会出现空白或者闪烁的问题呢?其实涉及到了浏览器的渲染机制。 我们知道浏览器有个刷新频率,一般是每秒60帧,也就是16.7毫秒重绘一次。那么当我们在清空画布并绘制图片的这个时间间隔内,如果绘制图片的时间超过了16.7毫秒,那么就会导致浏览器发生了重绘。 此时画布已经清空了,但新的图片还没绘制完成,就造成了视觉上的闪烁或者空白。

image.png

那么根据这个流程,我们可以从绘制时间这个角度继续分析,为什么这个过程会超过渲染帧的时间呢?我们看canvas绘制的接口不外乎两个:

drawImage putImageData 而绘制时间慢的罪魁祸首就在于drawImage这个接口(附上MDN链接),因为它能够将任意大小的图片在画布上绘制成任意大小。 那么这个任意大小就意味着,Canvas在绘制图片的过程中必然要对图片进行缩放或者截取,这就导致了效率的下降,也就造成了绘制时间超过了渲染帧。 使用双缓存

那找到了罪魁祸首以后就可以对症下药了,既然渲染过程慢是因为对图片大小缩放和压缩导致的,那我们只要保证大小一致就能确保绘制的速度了。 因此我们通过双缓存的方式来解决这个问题,通过另外创建一个和主画布等大的缓冲Canvas画布,需要渲染的图片先绘制到缓冲画布上,然后将缓冲画布的数据直接绘制到主画布上,就解决了闪烁的问题。

image.png

这里我们直接上代码:

function drawImage(url, mainCanvas) { //1. 第一步加载图片 const img = new Image(); img.src = url; img.onload = () => { //2.第二步,将图片绘制到缓存画布上,缓存画布临时创建和存储起来都可以 const cacheCanvas = document.createElement("canvas"); //画布设置为等大 cacheCanvas.width = mainCanvas.width; cacheCanvas.height = mainCanvas.height; //这里是绘制图片的逻辑,自适应或者拉伸取决于自己需要,为了方便这里就拉伸了 cacheCanvas.getContext("2d").drawImage(img, 0, 0, cacheCanvas.width, cacheCanvas.height); //3.第三步,把缓存画布的内容绘制到主画布上 const mainCtx = mainCanvas.getContext("2d"); //先清空上一帧 mainCtx.clearRect(0, 0, mainCanvas.width, mainCanvas.height); //绘制画面 mainCtx.drawImage(cacheCanvas, 0, 0); } } 2. 线条发散处理

在绘制一些规则的几何图形和线条的时候,可能会发现边缘或者线条明明宽度应该是1px,但结果却显示成了2px,而且颜色也更淡。 这其实跟Canvas绘制线条的规则有关,把屏幕像素点看成一个个网格,那么Canvas绘制线条时会以网格的交错点作为起点,因此奇数线宽的线条,往往宽度会被拆分到两边的网格之中。 又因为屏幕显示像素最小以1px为单位,因此两边的像素就会被自动补足发散。

理论结果

image.png

实际结果

image.png

解决方案

面对这种情况,我们可以做个特殊处理,如果线宽为奇数的情况下,我们就将绘制点偏移0.5px,最终的结果就不会发散了。

3. 不规则遮罩

有些时候我们可能需要利用Canvas实现一个遮罩的效果,比如裁剪框、刮刮乐。 对于规则图形,我们可以通过先绘制底部图案,然后调用clearRect的方法来擦除遮罩区域,但对于不规则的区域的话就没办法实现了。因此对于不规则图案,我们需要用到Canvas的一个功能来实现遮罩,那就是clip。对应的接口文档:CanvasRenderingContext2D.clip()

实现思路

实现的思路大致分为四步:

绘制作为遮罩的背景(比如纯黑、或者一张图片) 绘制需要露出来的形状(比如裁出一个五角星) 调用clip接口裁出这块区域 最后,如果需要镂空的话直接调用clearRect擦除画布即可;如果需要在镂空处绘制别的图案,可以在基础上调用绘制方法。 具体代码 function drawMask() { const cvs = document.createElement("canvas"); const ctx = cvs.getContext("2d"); cvs.width = 500; cvs.height = 500; //1. 绘制遮罩背景,这里使用纯黑 ctx.fillStyle = "#000"; ctx.fillRect(0, 0, 500, 500); //2. 绘制自定义的遮罩形状,这里用三角形比较简单 ctx.beginPath(); ctx.moveTo(250, 100); ctx.lineTo(375, 300); ctx.lineTo(125, 300); ctx.closePath(); //3. 调用clip接口裁出图形 ctx.clip(); //4. 擦除裁出的图形区域,这里只要保证擦除的区域包括裁剪区域即可 ctx.clearRect(0, 0, 500, 500) } 最终效果

image.png

4. Blob和ImageData数据的处理

有些场景下,你可能需要根据图片获取对应的ImageData数据或者Blob数据,传到后端做一些图像处理。这里就介绍下如何通过Canvas获得这两种数据,并且互相转换

获取ImageData

这里我们可以借助Canvas自带的getImageData接口获取ImageData数据。(Api的功能介绍直接看MDN就行了 CanvasRenderingContext2D.getImageData())。这里我们直接上代码:

function getImageData(url) { return new Promise((res, rej) => { //根据url创建出img对象来 const img = new Image(url); img.src = url; img.onload = () => { //创建临时的canvas元素,用于获取imageData数据 const tempCanvas = document.createElement("canvas"); //将canvas设为和图片等大 tempCanvas.width = img.naturalWidth; tempCanvas.height = img.naturalHeight; //绘制图片,并获取imageData数据 const ctx = tempCanvas.getContext("2d"); ctx.drawImage(img, 0, 0); res(ctx.getImageData(0, 0, tempCanvas.width, tempCanvas.height)) } img.onerror=(err)=>{ rej(err); } }) }

通过将图片绘制到Canvas上,再调用ctx.getImageData接口,我们就得到想要的ImageData数据了。

获取Blob

获取Blob数据可以借助Canvas自带的toBlob接口获取,(同样放上Api的链接HTMLCanvasElement.toBlob())。同样直接上代码:

function getBlob(url, type, quality) { return new Promise((res, rej) => { //根据url创建出img对象来 const img = new Image(url); img.src = url; img.onload = () => { //创建临时的canvas元素,用于获取imageData数据 const tempCanvas = document.createElement("canvas"); //将canvas设为和图片等大 tempCanvas.width = img.naturalWidth; tempCanvas.height = img.naturalHeight; //绘制图片,并获取imageData数据 const ctx = tempCanvas.getContext("2d"); ctx.drawImage(img, 0, 0); tempCanvas.toBlob(res, type, quality); } img.onerror = (err) => { rej(err); } }) }

用法基本和获取ImageDta类似,但最后是通过toBlob接口获取,同时可以设置数据类型和质量,相当于自带压缩了。

Blob转ImageData

其实本质上还是借助Canvas的几个接口,直接上代码:

function blobToImgData(blob) { const url = URL.createObjectURL(blob); return getImageData(url); }

你没看错!借助前面封装好的接口,其实只要两行代码就搞定了。

ImageData转Blob

这个过程稍微多几步,原理也是一样的:

function imgDataToBlob(imgData, type, quality) { return new Promise((res) => { //创建临时的canvas元素 const tempCanvas = document.createElement("canvas"); //将canvas元素设为图片等大 tempCanvas.width = imgData.width; tempCanvas.height = imgData.height; //绘制数据 tempCanvas.getContext("2d").putImageData(imgData, 0, 0); //转成blob数据 tempCanvas.toBlob(res, type, quality); }) } 5. 局部擦除重绘

当使用Canvas用于一些绘制图形较多的场景式,比如游戏、或者图形编辑器等等,需要频繁重绘,而每一次重绘都需要对整个画布所有的内容重新绘制,难免会造成很大的浪费。 很多时候其实需要重绘的图形比较独立,只需要重绘本身即可,因此如果能采用局部擦除重绘的方式的话,自然能节省渲染的成本。

不同的渲染情况

我们分析一下可能遇到的不同的渲染情况:

完全独立

image.png 这种是最简单的情况,我们只需要擦除掉图形2自身的区域,然后重绘图形2即可。

两个独立的图形重叠

image.png 当图形2与图形3发生了重叠,那我们在重绘图形2的同时势必要重绘图形3。 如果只针对这种情况的话,简单点的做法是,直接擦除掉图形2和图形3组成的整个包围盒区域,然后全部重绘即可。

image.png

多个图形连续重叠

image.png 这种情况其实是最头疼的,如果按照上一种情况的做法,大致思路会是这样:

图形2需要重绘,判断有无图形重叠 图形3和图形2重叠,因此要重绘图形3,并判断有无图形和图形3重叠 图形1和图形3重叠,因此要重绘图形1,并判断有无图形和图形1重叠 擦除图形1、2、3形成的包围盒区域,同时重绘图形1、2、3 可以看到,这样的话当图形数量巨大,且大多重叠的时候,很可能经过了大量的计算以后,最终的结果还是要重绘所有的图形。 因此,我们需要采用另一种方式,借助我们刚刚提到的一个Canvas相关的接口,clip来实现。 如何实现局部擦除重绘

我们可以通过clip接口裁剪出需要重绘的图形区域,将重绘的影响限制到图形自身的区域范围内。

image.png

比如这种情况下,我们需要重绘图形2,那我们要做的其实就这么几步:

利用clip裁剪出图像2的绘制区域 调用clearRect擦除这块区域 重绘和这块区域重叠的所有图形

比起之前的做法的优势在于,如果和图形2重叠的图形上还有重叠的图形,我们就不需要考虑了,只考虑和图形2重叠的图形即可。

image.png

总结

这篇文章主要总结了5个实用的Canvas使用技巧,要结合实际场景进行使用,相信用得好的话一定会对你有所帮助~ 另外,这里举得都是我个人有使用过并觉得比较实用的,如果有错误或者遗漏的地方欢迎指出~ 如果你有别的实用技巧的话也可以分享~

写在最后 很感谢你能看到这里,不妨点个赞支持一下,万分感激~! 以后会更新更多文章和知识点,感兴趣的话可以关注一波~


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有